package com.copa.ums.modules.ums.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.copa.ums.common.api.CommonPage;
import com.copa.ums.common.util.IpUtil;
import com.copa.ums.common.domain.UmsUserDetails;
import com.copa.ums.common.exception.Asserts;
import com.copa.ums.common.vo.ResponseVo;
import com.copa.ums.modules.ums.dto.UmsUserLoginDto;
import com.copa.ums.modules.ums.dto.UmsUserDto;
import com.copa.ums.modules.ums.dto.UpdateUserPasswordDto;
import com.copa.ums.modules.ums.model.*;
import com.copa.ums.modules.ums.mapper.UmsUserMapper;
import com.copa.ums.modules.ums.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.copa.ums.security.util.JwtTokenUtil;
import com.copa.ums.security.util.SpringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author copa
 * @since 2022-10-31
 */
@Service
public class UmsUserServiceImpl extends ServiceImpl<UmsUserMapper, UmsUser> implements UmsUserService {

    private static final Logger LOGGER = LoggerFactory.getLogger(UmsUserServiceImpl.class);

    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private UmsResourceService resourceService;

    @Resource
    private JwtTokenUtil jwtTokenUtil;

    @Resource
    private UmsUserLoginLogService loginLogService;

    @Resource
    private UmsMenuService menuService;

    @Resource
    private UmsRoleService roleService;

    @Resource
    private UmsUserRoleService umsUserRoleService;

    @Resource
    private UmsUserMapper userMapper;

    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseVo<UmsUser> register(UmsUserDto umsUserDto) {
        UmsUser user = new UmsUser();
        BeanUtils.copyProperties(umsUserDto, user);
        QueryWrapper<UmsUser> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(UmsUser::getUsername, user.getUsername());
        List<UmsUser> umsUserList = list(wrapper);
        if (CollUtil.isNotEmpty(umsUserList)) {
            return ResponseVo.failed("该用户名已存在~请另起一个吧~");
        }
        //将密码进行加密操作
        String encodePassword = passwordEncoder.encode(user.getPassword());
        user.setPassword(encodePassword);
        if (baseMapper.insert(user) <= 0) {
            return ResponseVo.failed("系统繁忙，请稍后再试~");
        }
        return ResponseVo.success(user);
    }

    @Override
    public UserDetails loadUserByUsername(String username) {
        //获取用户信息
        UmsUser user = getUserByUsername(username);
        if (user != null) {
            List<UmsResource> resourceList = getResourceList(user.getId());
            return new UmsUserDetails(user, resourceList);
        }
        throw new UsernameNotFoundException("用户名或密码错误");
    }

    @Override
    public UmsUser getUserByUsername(String username) {
        UmsUser user = getCacheService().getUser(username);
        if (Objects.nonNull(user)) {
            return user;
        }
        QueryWrapper<UmsUser> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(UmsUser::getUsername, username);
        List<UmsUser> userList = list(wrapper);
        if (CollUtil.isNotEmpty(userList)) {
            user = userList.get(0);
            getCacheService().setUser(user);
            return user;
        }
        return null;
    }

    @Override
    public List<UmsResource> getResourceList(Long userId) {
        List<UmsResource> resourceList = getCacheService().getResourceList(userId);
        if(CollUtil.isNotEmpty(resourceList)){
            return resourceList;
        }
        resourceList = resourceService.getResourceList(userId);
        if(CollUtil.isNotEmpty(resourceList)){
            getCacheService().setResourceList(userId, resourceList);
        }
        return resourceList;
    }

    @Override
    public UmsUserCacheService getCacheService() {
        return SpringUtil.getBean(UmsUserCacheService.class);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseVo<Map<String, String>> login(UmsUserLoginDto umsUserLoginDto) {
        String token = null;
        // 密码需要客户端加密后传递
        try {
            UserDetails userDetails = loadUserByUsername(umsUserLoginDto.getUsername());
            if(!passwordEncoder.matches(umsUserLoginDto.getPassword(), userDetails.getPassword())){
                Asserts.fail("密码不正确");
            }
            if(!userDetails.isEnabled()){
                Asserts.fail("帐号已被禁用");
            }
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authentication);
            token = jwtTokenUtil.generateToken(userDetails);
            insertLoginLog(umsUserLoginDto.getUsername());
        } catch (AuthenticationException e) {
            LOGGER.warn("登录异常:{}", e.getMessage());
        }
        if (StrUtil.isEmpty(token)) {
            return ResponseVo.validateFailed("用户名或密码错误~");
        }
        Map<String, String> tokenMap = new HashMap<>();
        tokenMap.put("token", token);
        tokenMap.put("tokenHead", tokenHead);
        return ResponseVo.success(tokenMap);
    }

    @Override
    public ResponseVo<Map<String, String>> refreshToken(String oldToken) {
        String refreshToken = jwtTokenUtil.refreshHeadToken(oldToken);
        if (refreshToken == null) {
            return ResponseVo.failed("token已经过期！");
        }
        Map<String, String> tokenMap = new HashMap<>();
        tokenMap.put("token", refreshToken);
        tokenMap.put("tokenHead", tokenHead);
        return ResponseVo.success(tokenMap);
    }

    @Override
    public ResponseVo<Map<String, Object>> getUserInfo(String username) {
        UmsUser user = getUserByUsername(username);
        Map<String, Object> data = new HashMap<>();
        data.put("username", user.getUsername());
        data.put("icon", user.getIcon());
        data.put("menus", menuService.getMenuList(user.getId()));
        List<UmsRole> roleList = roleService.getRoleList(user.getId());
        if (CollUtil.isNotEmpty(roleList)){
            List<String> roles = roleList.stream().map(UmsRole::getName).collect(Collectors.toList());
            data.put("roles", roles);
        }
        return ResponseVo.success(data);
    }

    @Override
    public ResponseVo<CommonPage<UmsUser>> list(String keyword, Integer pageSize, Integer pageNum) {
        Page<UmsUser> page = new Page<>(pageNum, pageSize);
        QueryWrapper<UmsUser> wrapper = new QueryWrapper<>();
        LambdaQueryWrapper<UmsUser> lambda = wrapper.lambda();
        if(StrUtil.isNotEmpty(keyword)){
            lambda.like(UmsUser::getUsername,keyword);
            lambda.or().like(UmsUser::getNickName,keyword);
        }
        return ResponseVo.success(CommonPage.restPage(page(page, wrapper)));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseVo<String> update(Long userId, UmsUser user) {
        user.setId(userId);
        UmsUser rawUser = getById(userId);
        if (rawUser.getPassword().equals(user.getPassword())){
            //与原加密密码相同的不需要修改
            user.setPassword(null);
        } else {
            //与原加密密码不同的需要加密修改
            if (StrUtil.isEmpty(user.getPassword())){
                user.setPassword(null);
            } else {
                user.setPassword(passwordEncoder.encode(user.getPassword()));
            }
        }
        boolean flag = updateById(user);
        if (flag) {
            getCacheService().delUser(userId);
            return ResponseVo.success(null);
        }
        return ResponseVo.failed();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseVo<String> updatePassword(UpdateUserPasswordDto updateUserPasswordDto) {
        if(StrUtil.isEmpty(updateUserPasswordDto.getUsername())
                || StrUtil.isEmpty(updateUserPasswordDto.getOldPassword())
                || StrUtil.isEmpty(updateUserPasswordDto.getNewPassword())){
            return ResponseVo.failed("提交参数不合法");
        }
        QueryWrapper<UmsUser> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(UmsUser::getUsername, updateUserPasswordDto.getUsername());
        List<UmsUser> userList = list(wrapper);
        if(CollUtil.isEmpty(userList)){
            return ResponseVo.failed("找不到该用户");
        }
        UmsUser user = userList.get(0);
        if(!passwordEncoder.matches(updateUserPasswordDto.getOldPassword(), user.getPassword())){
            return ResponseVo.failed("旧密码错误");
        }
        user.setPassword(passwordEncoder.encode(updateUserPasswordDto.getNewPassword()));
        updateById(user);
        getCacheService().delUser(user.getId());
        return ResponseVo.success(null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseVo<String> delete(Long userId) {
        getCacheService().delUser(userId);
        boolean flag = removeById(userId);
        if (!flag) {
            return ResponseVo.failed();
        }
        getCacheService().delResourceList(userId);
        return ResponseVo.success(null);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ResponseVo<Integer> updateRole(Long userId, List<Long> roleIds) {
        int count = roleIds == null ? 0 : roleIds.size();
        if (count < 0) {
            return ResponseVo.failed();
        }
        // 先删除原来的关系
        QueryWrapper<UmsUserRole> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(UmsUserRole::getUserId, userId);
        umsUserRoleService.remove(wrapper);
        // 建立新关系
        if (!CollectionUtils.isEmpty(roleIds)) {
            List<UmsUserRole> list = new ArrayList<>();
            for (Long roleId : roleIds) {
                UmsUserRole roleRelation = new UmsUserRole();
                roleRelation.setUserId(userId);
                roleRelation.setRoleId(roleId);
                list.add(roleRelation);
            }
            umsUserRoleService.saveBatch(list);
        }
        getCacheService().delResourceList(userId);
        return ResponseVo.failed();
    }

    @Override
    public ResponseVo<List<UmsRole>> getRoleList(Long userId) {
        return ResponseVo.success(roleService.getRoleList(userId));
    }

    @Override
    public List<Long> getUserIdListByResourceId(Long resourceId) {
        return userMapper.getUserIdListByResourceId(resourceId);
    }

    @Override
    public ResponseVo<String> logout(String username) {
        getCacheService().delUser(getUserByUsername(username).getId());
        return ResponseVo.success(null);
    }

    private void insertLoginLog(String username) {
        UmsUser user = getUserByUsername(username);
        if (Objects.isNull(user)) {
            return;
        }
        UmsUserLoginLog loginLog = new UmsUserLoginLog();
        loginLog.setUserId(user.getId());
        //loginLog.setCreateTime(new Date());
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        loginLog.setIp(IpUtil.getIpAddr(request));
        loginLog.setAddress(IpUtil.getIpInfo(loginLog.getIp()));
        loginLog.setUserAgent(request.getHeader("user-agent"));
        loginLogService.save(loginLog);
        user.setLoginTime(LocalDateTime.now());
        updateById(user);
    }
}
